// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/loader/throttling_resource_handler.h"

#include <utility>

#include "content/browser/loader/resource_controller.h"
#include "content/public/common/resource_response.h"
#include "net/url_request/url_request.h"

namespace content {

ThrottlingResourceHandler::ThrottlingResourceHandler(
    std::unique_ptr<ResourceHandler> next_handler,
    net::URLRequest* request,
    std::vector<std::unique_ptr<ResourceThrottle>> throttles)
    : LayeredResourceHandler(request, std::move(next_handler))
    , deferred_stage_(DEFERRED_NONE)
    , throttles_(std::move(throttles))
    , next_index_(0)
    , cancelled_by_resource_throttle_(false)
{
    for (const auto& throttle : throttles_) {
        throttle->set_delegate(this);
        // Throttles must have a name, as otherwise, bugs where a throttle fails
        // to resume a request can be very difficult to debug.
        DCHECK(throttle->GetNameForLogging());
    }
}

ThrottlingResourceHandler::~ThrottlingResourceHandler()
{
}

bool ThrottlingResourceHandler::OnRequestRedirected(
    const net::RedirectInfo& redirect_info,
    ResourceResponse* response,
    bool* defer)
{
    DCHECK(!cancelled_by_resource_throttle_);
    DCHECK(!*defer);

    while (next_index_ < throttles_.size()) {
        int index = next_index_;
        throttles_[index]->WillRedirectRequest(redirect_info, defer);
        next_index_++;
        if (cancelled_by_resource_throttle_)
            return false;
        if (*defer) {
            OnRequestDeferred(index);
            deferred_stage_ = DEFERRED_REDIRECT;
            deferred_redirect_ = redirect_info;
            deferred_response_ = response;
            return true; // Do not cancel.
        }
    }

    next_index_ = 0; // Reset for next time.

    return next_handler_->OnRequestRedirected(redirect_info, response, defer);
}

bool ThrottlingResourceHandler::OnWillStart(const GURL& url, bool* defer)
{
    DCHECK(!cancelled_by_resource_throttle_);
    DCHECK(!*defer);

    while (next_index_ < throttles_.size()) {
        int index = next_index_;
        throttles_[index]->WillStartRequest(defer);
        next_index_++;
        if (cancelled_by_resource_throttle_)
            return false;
        if (*defer) {
            OnRequestDeferred(index);
            deferred_stage_ = DEFERRED_START;
            deferred_url_ = url;
            return true; // Do not cancel.
        }
    }

    next_index_ = 0; // Reset for next time.

    return next_handler_->OnWillStart(url, defer);
}

bool ThrottlingResourceHandler::OnResponseStarted(ResourceResponse* response,
    bool* defer)
{
    DCHECK(!cancelled_by_resource_throttle_);
    DCHECK(!*defer);

    while (next_index_ < throttles_.size()) {
        int index = next_index_;
        throttles_[index]->WillProcessResponse(defer);
        next_index_++;
        if (cancelled_by_resource_throttle_)
            return false;
        if (*defer) {
            OnRequestDeferred(index);
            deferred_stage_ = DEFERRED_RESPONSE;
            deferred_response_ = response;
            return true; // Do not cancel.
        }
    }

    next_index_ = 0; // Reset for next time.

    return next_handler_->OnResponseStarted(response, defer);
}

void ThrottlingResourceHandler::Cancel()
{
    cancelled_by_resource_throttle_ = true;
    controller()->Cancel();
}

void ThrottlingResourceHandler::CancelAndIgnore()
{
    cancelled_by_resource_throttle_ = true;
    controller()->CancelAndIgnore();
}

void ThrottlingResourceHandler::CancelWithError(int error_code)
{
    cancelled_by_resource_throttle_ = true;
    controller()->CancelWithError(error_code);
}

void ThrottlingResourceHandler::Resume()
{
    // Throttles expect to be able to cancel requests out-of-band, so just do
    // nothing if one request resumes after another cancels. Can't even recognize
    // out-of-band cancels and for synchronous teardown, since don't know if the
    // currently active throttle called Cancel() or if it was another one.
    if (cancelled_by_resource_throttle_)
        return;

    DeferredStage last_deferred_stage = deferred_stage_;
    deferred_stage_ = DEFERRED_NONE;
    // Clear information about the throttle that delayed the request.
    request()->LogUnblocked();
    switch (last_deferred_stage) {
    case DEFERRED_NONE:
        NOTREACHED();
        break;
    case DEFERRED_START:
        ResumeStart();
        break;
    case DEFERRED_REDIRECT:
        ResumeRedirect();
        break;
    case DEFERRED_RESPONSE:
        ResumeResponse();
        break;
    }
}

void ThrottlingResourceHandler::ResumeStart()
{
    DCHECK(!cancelled_by_resource_throttle_);

    GURL url = deferred_url_;
    deferred_url_ = GURL();

    bool defer = false;
    if (!OnWillStart(url, &defer)) {
        controller()->Cancel();
    } else if (!defer) {
        controller()->Resume();
    }
}

void ThrottlingResourceHandler::ResumeRedirect()
{
    DCHECK(!cancelled_by_resource_throttle_);

    net::RedirectInfo redirect_info = deferred_redirect_;
    deferred_redirect_ = net::RedirectInfo();
    scoped_refptr<ResourceResponse> response;
    deferred_response_.swap(response);

    bool defer = false;
    if (!OnRequestRedirected(redirect_info, response.get(), &defer)) {
        controller()->Cancel();
    } else if (!defer) {
        controller()->Resume();
    }
}

void ThrottlingResourceHandler::ResumeResponse()
{
    DCHECK(!cancelled_by_resource_throttle_);

    scoped_refptr<ResourceResponse> response;
    deferred_response_.swap(response);

    bool defer = false;
    if (!OnResponseStarted(response.get(), &defer)) {
        controller()->Cancel();
    } else if (!defer) {
        controller()->Resume();
    }
}

void ThrottlingResourceHandler::OnRequestDeferred(int throttle_index)
{
    request()->LogBlockedBy(throttles_[throttle_index]->GetNameForLogging());
}

} // namespace content
